<template>
  <div
    class="slide-verify"
    :style="{ width: canvasWidth + 'px' }"
    @selectstart.prevent="false"
  >
    <!-- 图片加载遮蔽罩 -->
    <div
      :class="{ 'img-loading': isLoading }"
      :style="{ height: canvasHeight + 'px' }"
      v-if="isLoading"
    />
    
    <!-- 认证成功后的文字提示 -->
    <div
      class="success-hint"
      :style="{ height: canvasHeight + 'px' }"
      v-if="verifySuccess"
    >
      {{ successHint }}
    </div>
    
    <!-- 刷新按钮 -->
    <div class="refresh-icon" @click="refresh">
      <ElIcon size="18">
        <Refresh />
      </ElIcon>
    </div>
    
    <!-- 前端生成 -->
    <template v-if="isFrontCheck">
      <!-- 验证图片 -->
      <canvas
        ref="canvas"
        class="slide-canvas"
        :width="canvasWidth"
        :height="canvasHeight"
      />
      <!-- 阻塞块 -->
      <canvas
        ref="block"
        class="slide-block"
        :width="canvasWidth"
        :height="canvasHeight"
      />
    </template>
    
    <!-- 后端生成 -->
    <template v-else>
      <!-- 验证图片 -->
      <img
        ref="canvas"
        class="slide-canvas"
        :width="canvasWidth"
        :height="canvasHeight"
      />
      <!-- 阻塞块 -->
      <img
        ref="block"
        :class="['slide-block', { 'verify-fail': verifyFail }]"
      />
    </template>
    
    <!-- 滑动条 -->
    <div
      class="slider"
      :class="{
        'verify-active': verifyActive,
        'verify-success': verifySuccess,
        'verify-fail': verifyFail,
      }"
    >
      <!-- 滑块 -->
      <div class="slider-box" :style="{ width: sliderBoxWidth }">
        <!-- 按钮 -->
        <div
          class="slider-button"
          ref="sliderButton"
          :style="{ left: sliderButtonLeft }"
        >
          <!-- 按钮图标 -->
          <div class="slider-button-icon">
            <ElIcon size="20">
              <ArrowRight />
            </ElIcon>
          </div>
        </div>
      </div>
      <!-- 滑动条提示文字 -->
      <span class="slider-hint">{{ sliderHint }}</span>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import { ElIcon, ElMessage } from 'element-plus';
import { ArrowRight, Refresh } from '@element-plus/icons-vue';
import { getCaptchaApi } from "@/api/auth";

// 定义组件属性
const props = defineProps({
  // 阻塞块长度
  blockLength: {
    type: Number,
    default: 42,
  },
  // 阻塞块弧度
  blockRadius: {
    type: Number,
    default: 10,
  },
  // 画布宽度
  canvasWidth: {
    type: Number,
    default: 320,
  },
  // 画布高度
  canvasHeight: {
    type: Number,
    default: 155,
  },
  // 滑块操作提示
  sliderHint: {
    type: String,
    default: "向右滑动",
  },
  // 可允许的误差范围
  accuracy: {
    type: Number,
    default: 3,
  },
  // 图片资源数组
  imageList: {
    type: Array,
    default: () => [],
  },
});

// 定义组件事件
const emit = defineEmits(['success', 'fail', 'again']);

// 响应式变量
const isFrontCheck = ref(false); // 前端校验标记
const verifyActive = ref(false); // 校验进行状态
const verifySuccess = ref(false); // 校验成功状态
const verifyFail = ref(false); // 校验失败状态
const canvas = ref(null); // 主画布引用
const block = ref(null); // 滑块引用
const canvasCtx = ref(null); // 主画布上下文
const blockCtx = ref(null); // 滑块画布上下文
const blockWidth = ref(props.blockLength * 2); // 阻塞块宽度
const blockX = ref(0); // 阻塞块X坐标
const blockY = ref(0); // 阻塞块Y坐标
const image = ref(null); // 图片对象
const originX = ref(0); // 起始X坐标
const originY = ref(0); // 起始Y坐标
const dragDistanceList = ref([]); // 拖动距离记录
const sliderBoxWidth = ref('0px'); // 滑块盒子宽度
const sliderButtonLeft = ref('0px'); // 滑块按钮位置
const isMouseDown = ref(false); // 鼠标按下状态
const isLoading = ref(true); // 加载状态
const timestamp = ref(0); // 时间戳
const successHint = ref(""); // 成功提示文本
const nonceStr = ref(""); // 随机字符串
const sliderButton = ref(null); // 滑块按钮引用

// 工具函数
const sum = (x, y) => x + y;
const square = (x) => x * x;

// 初始化
onMounted(() => {
  init();
});

// 初始化函数
const init = () => {
  initDom();
  bindEvents();
};

// 初始化DOM
const initDom = () => {
  if (isFrontCheck.value) {
    canvasCtx.value = canvas.value?.getContext("2d");
    blockCtx.value = block.value?.getContext("2d");
    initImage();
  } else {
    getCaptcha();
  }
};

// 从后端获取验证码
const getCaptcha = async () => {
  try {
    const response = await getCaptchaApi({});
    const data = response.data;
    nonceStr.value = data.nonceStr;
    block.value.src = data.blockSrc;
    block.value.style.top = data.blockY + "px";
    canvas.value.src = data.canvasSrc;
  } catch (error) {
    ElMessage.error('获取验证码失败，请重试');
  } finally {
    isLoading.value = false;
  }
};

// 初始化前端验证码图片
const initImage = () => {
  const img = new Image();
  img.crossOrigin = "Anonymous";
  
  img.onload = () => {
    drawBlock();
    const { canvasWidth, canvasHeight } = props;
    
    // 绘制原图
    canvasCtx.value.drawImage(img, 0, 0, canvasWidth, canvasHeight);
    blockCtx.value.drawImage(img, 0, 0, canvasWidth, canvasHeight);
    
    // 处理滑块
    const yAxle = blockY.value - props.blockRadius * 2;
    const imageData = blockCtx.value.getImageData(
      blockX.value,
      yAxle,
      blockWidth.value,
      blockWidth.value
    );
    
    block.value.width = blockWidth.value;
    blockCtx.value.putImageData(imageData, 0, yAxle);
    
    // 加载完成
    isLoading.value = false;
    nonceStr.value = "loyer";
  };
  
  img.onerror = () => {
    ElMessage.warning('图片加载失败，正在重试...');
    setTimeout(() => {
      img.src = getImageSrc();
    }, 1000);
  };
  
  img.src = getImageSrc();
  image.value = img;
};

// 创建图片对象
const createImage = (onload) => {
  const img = new Image();
  img.crossOrigin = "Anonymous";
  img.onload = onload;
  img.onerror = () => {
    ElMessage.warning('图片加载失败');
  };
  img.src = getImageSrc();
  return img;
};

// 获取图片源地址
const getImageSrc = () => {
  const len = props.imageList.length;
  if (len > 0) {
    return props.imageList[getNonceByRange(0, len)];
  } else {
    // 使用随机图片服务
    return `https://picsum.photos/seed/${Date.now()}/${props.canvasWidth}/${props.canvasHeight}`;
  }
};

// 生成指定范围的随机数
const getNonceByRange = (start, end) => {
  return Math.round(Math.random() * (end - start) + start);
};

// 绘制滑块
const drawBlock = () => {
  blockX.value = getNonceByRange(
    blockWidth.value + 10,
    props.canvasWidth - (blockWidth.value + 10)
  );
  blockY.value = getNonceByRange(
    10 + props.blockRadius * 2,
    props.canvasHeight - (blockWidth.value + 10)
  );
  
  draw(canvasCtx.value, "fill");
  draw(blockCtx.value, "clip");
};

// 绘制函数
const draw = (ctx, operation) => {
  const PI = Math.PI;
  const x = blockX.value;
  const y = blockY.value;
  const l = props.blockLength;
  const r = props.blockRadius;
  
  ctx.beginPath();
  ctx.moveTo(x, y);
  ctx.arc(x + l / 2, y - r + 2, r, 0.72 * PI, 2.26 * PI);
  ctx.lineTo(x + l, y);
  ctx.arc(x + l + r - 2, y + l / 2, r, 1.21 * PI, 2.78 * PI);
  ctx.lineTo(x + l, y + l);
  ctx.lineTo(x, y + l);
  ctx.arc(x + r - 2, y + l / 2, r + 0.4, 2.76 * PI, 1.24 * PI, true);
  ctx.lineTo(x, y);
  
  ctx.lineWidth = 2;
  ctx.fillStyle = "rgba(255, 255, 255, 0.9)";
  ctx.strokeStyle = "rgba(255, 255, 255, 0.9)";
  ctx.stroke();
  ctx[operation]();
  ctx.globalCompositeOperation = "destination-over";
};

// 绑定事件
const bindEvents = () => {
  // 鼠标事件
  const handleMouseDown = (event) => {
    startEvent(event.clientX, event.clientY);
  };
  
  const handleMouseMove = (event) => {
    moveEvent(event.clientX, event.clientY);
  };
  
  const handleMouseUp = (event) => {
    endEvent(event.clientX);
  };
  
  // 触摸事件
  const handleTouchStart = (event) => {
    startEvent(
      event.changedTouches[0].pageX,
      event.changedTouches[0].pageY
    );
  };
  
  const handleTouchMove = (event) => {
    moveEvent(
      event.changedTouches[0].pageX,
      event.changedTouches[0].pageY
    );
  };
  
  const handleTouchEnd = (event) => {
    endEvent(event.changedTouches[0].pageX);
  };
  
  // 绑定事件
  sliderButton.value?.addEventListener('mousedown', handleMouseDown);
  document.addEventListener('mousemove', handleMouseMove);
  document.addEventListener('mouseup', handleMouseUp);
  sliderButton.value?.addEventListener('touchstart', handleTouchStart);
  document.addEventListener('touchmove', handleTouchMove);
  document.addEventListener('touchend', handleTouchEnd);
};

// 检查图片是否有效
const checkImgSrc = () => {
  if (isFrontCheck.value) {
    return true;
  }
  return !!canvas.value?.src;
};

// 开始滑动
const startEvent = (x, y) => {
  if (!checkImgSrc() || isLoading.value || verifySuccess.value) {
    return;
  }
  
  originX.value = x;
  originY.value = y;
  isMouseDown.value = true;
  timestamp.value = Date.now();
  dragDistanceList.value = [];
};

// 滑动过程
const moveEvent = (x, y) => {
  if (!isMouseDown.value) {
    return false;
  }
  
  const moveX = x - originX.value;
  const moveY = y - originY.value;
  
  // 限制滑动范围
  if (moveX < 0 || moveX + 40 >= props.canvasWidth) {
    return false;
  }
  
  // 更新滑块位置
  sliderButtonLeft.value = moveX + "px";
  const blockLeft = ((props.canvasWidth - 40 - 20) / (props.canvasWidth - 40)) * moveX;
  block.value.style.left = blockLeft + "px";
  
  verifyActive.value = true;
  sliderBoxWidth.value = moveX + "px";
  dragDistanceList.value.push(moveY);
};

// 结束滑动
const endEvent = (x) => {
  if (!isMouseDown.value) {
    return false;
  }
  
  isMouseDown.value = false;
  
  // 未移动不处理
  if (x === originX.value) {
    return false;
  }
  
  // 开始验证
  isLoading.value = true;
  verifyActive.value = false;
  
  // 计算滑动时间
  timestamp.value = Date.now() - timestamp.value;
  
  // 移动距离
  const moveLength = parseInt(block.value.style.left || 0);
  
  // 验证逻辑
  if (timestamp.value > 10000) {
    // 超时
    verifyFailEvent('操作超时');
  } else if (!turingTest()) {
    // 非人为操作
    verifyFail.value = true;
    emit('again');
  } else if (isFrontCheck.value) {
    // 前端验证
    const accuracy = Math.max(1, Math.min(10, props.accuracy));
    const success = Math.abs(moveLength - blockX.value) <= accuracy;
    
    if (!success) {
      verifyFailEvent('验证失败，请重试');
    } else {
      verifySuccessEvent();
      emit('success', { 
        nonceStr: nonceStr.value, 
        value: moveLength 
      });
    }
  } else {
    // 后端验证
    emit('success', { 
      nonceStr: nonceStr.value, 
      value: moveLength 
    });
  }
};

// 图灵测试 - 判断是否为人为操作
const turingTest = () => {
  const arr = dragDistanceList.value;
  if (arr.length <= 1) return true;
  
  const average = arr.reduce(sum) / arr.length;
  const deviations = arr.map(x => x - average);
  const stdDev = Math.sqrt(deviations.map(square).reduce(sum) / arr.length);
  
  return average !== stdDev;
};

// 验证成功处理
const verifySuccessEvent = () => {
  isLoading.value = false;
  verifySuccess.value = true;
  
  const elapsedTime = (timestamp.value / 1000).toFixed(1);
  if (elapsedTime < 1) {
    successHint.value = `仅仅${elapsedTime}S，你的速度快如闪电`;
  } else if (elapsedTime < 2) {
    successHint.value = `只用了${elapsedTime}S，这速度简直完美`;
  } else {
    successHint.value = `耗时${elapsedTime}S，争取下次再快一点`;
  }
};

// 验证失败处理
const verifyFailEvent = (msg) => {
  verifyFail.value = true;
  emit('fail', msg);
  ElMessage.error(msg || '验证失败，请重试');
  refresh();
};

// 刷新验证码
const refresh = () => {
  // 延迟重置失败状态，等待动画完成
  setTimeout(() => {
    verifyFail.value = false;
  }, 500);
  
  // 重置状态
  isLoading.value = true;
  verifyActive.value = false;
  verifySuccess.value = false;
  block.value.style.left = '0px';
  sliderBoxWidth.value = '0px';
  sliderButtonLeft.value = '0px';
  
  // 重新加载验证码
  if (isFrontCheck.value) {
    // 清除画布
    if (canvasCtx.value && blockCtx.value) {
      canvasCtx.value.clearRect(0, 0, props.canvasWidth, props.canvasHeight);
      blockCtx.value.clearRect(0, 0, props.canvasWidth, props.canvasHeight);
      block.value.width = props.canvasWidth;
    }
    initImage();
  } else {
    getCaptcha();
  }
};

// 暴露方法给父组件
defineExpose({
  refresh,
  verifySuccessEvent
});
</script>

<style scoped lang="scss">
.slide-verify {
  position: relative;
}

/* 图片加载样式 */
.img-loading {
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  bottom: 0;
  z-index: 999;
  animation: loading 1.5s infinite;
  background: #737c8e url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 50 50'%3E%3Cpath fill='%23ffffff' d='M25.251,6.461c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615V6.461z'%3E%3CanimateTransform attributeType='xml' attributeName='transform' type='rotate' from='0 25 25' to='360 25 25' dur='0.6s' repeatCount='indefinite'/%3E%3C/path%3E%3C/svg%3E") no-repeat center;
  background-size: 100px;
  border-radius: 5px;
}

@keyframes loading {
  0% { opacity: 0.7; }
  100% { opacity: 0.9; }
}

/* 认证成功提示 */
.success-hint {
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  z-index: 999;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.8);
  color: #52c41a;
  font-size: 14px;
}

/* 刷新按钮 */
.refresh-icon {
  position: absolute;
  right: 0;
  top: 0;
  width: 35px;
  height: 35px;
  cursor: pointer;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #909399;
  transition: all 0.2s;
  
  &:hover {
    color: #409eff;
    background-color: rgba(0, 0, 0, 0.05);
    border-radius: 4px;
  }
}

/* 验证图片 */
.slide-canvas {
  border-radius: 5px;
}

/* 阻塞块 */
.slide-block {
  position: absolute;
  left: 0;
  top: 0;
}

/* 校验失败时的阻塞块样式 */
.slide-block.verify-fail {
  transition: left 0.5s linear;
}

/* 滑动条 */
.slider {
  position: relative;
  text-align: center;
  width: 100%;
  height: 40px;
  line-height: 40px;
  margin-top: 15px;
  background: #f7f9fa;
  color: #45494c;
  border: 1px solid #e4e7eb;
  border-radius: 5px;
}

/* 滑动盒子 */
.slider-box {
  position: absolute;
  left: 0;
  top: 0;
  height: 40px;
  border: 0 solid #409eff;
  background: #d1e9fe;
  border-radius: 5px;
}

/* 滑动按钮 */
.slider-button {
  position: absolute;
  top: 0;
  left: 0;
  width: 40px;
  height: 40px;
  background: #fff;
  box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
  cursor: pointer;
  transition: all 0.2s;
  border-radius: 5px;
  display: flex;
  align-items: center;
  justify-content: center;
  
  &:hover {
    background: #409eff;
    color: #fff;
  }
}

/* 校验中的按钮样式 */
.verify-active .slider-button {
  height: 38px;
  top: -1px;
  border: 1px solid #409eff;
}

/* 校验中的滑动盒样式 */
.verify-active .slider-box {
  height: 38px;
  border-width: 1px;
}

/* 校验成功的滑动盒样式 */
.verify-success .slider-box {
  height: 38px;
  border: 1px solid #52c41a;
  background-color: #f6ffed;
}

/* 校验成功的按钮样式 */
.verify-success .slider-button {
  height: 38px;
  top: -1px;
  border: 1px solid #52c41a;
  background-color: #52c41a !important;
  color: #fff !important;
}

/* 校验失败的滑动盒样式 */
.verify-fail .slider-box {
  height: 38px;
  border: 1px solid #f5222d;
  background-color: #fff1f0;
  transition: width 0.5s linear;
}

/* 校验失败的按钮样式 */
.verify-fail .slider-button {
  height: 38px;
  top: -1px;
  border: 1px solid #f5222d;
  background-color: #f5222d !important;
  color: #fff !important;
  transition: left 0.5s linear;
}

/* 校验状态下隐藏提示文字 */
.verify-active .slider-hint,
.verify-success .slider-hint,
.verify-fail .slider-hint {
  display: none;
}
</style>